package com.wbp.cms.client.util;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;


public abstract class ToStringStyle implements Serializable {
  /**
   * 
   */
  private static final long serialVersionUID = 167068389039553901L;
  /**
   * The default toString style. Using the Using the <code>Person</code> example from
   * {@link ToStringBuilder}, the output would look like this:
   * <p/>
   * 
   * <pre>
   * Person@182f0db[name=John Doe,age=33,smoker=false]
   * </pre>
   */
  public static final ToStringStyle DEFAULT_STYLE = new DefaultToStringStyle();
  public static final String LINE_SEPARATOR = getSystemProperty("line.separator");

  private static String getSystemProperty(String property) {
    try {
      return System.getProperty(property);
    } catch (SecurityException ex) {
      // we are not allowed to look at this property
      System.err.println("Caught a SecurityException reading the system property '" + property
          + "'; the SystemUtils property value will default to null.");
      return null;
    }
  }

  /**
   * The multi line toString style. Using the Using the <code>Person</code> example from
   * {@link ToStringBuilder}, the output would look like this:
   * <p/>
   * 
   * <pre>
   * Person@182f0db[
   *   name=John Doe
   *   age=33
   *   smoker=false
   * ]
   * </pre>
   */
  public static final ToStringStyle MULTI_LINE_STYLE = new MultiLineToStringStyle();

  /**
   * The no field names toString style. Using the Using the <code>Person</code> example from
   * {@link ToStringBuilder}, the output would look like this:
   * <p/>
   * 
   * <pre>
   * Person@182f0db[John Doe,33,false]
   * </pre>
   */
  public static final ToStringStyle NO_FIELD_NAMES_STYLE = new NoFieldNameToStringStyle();

  /**
   * The short prefix toString style. Using the <code>Person</code> example from
   * {@link ToStringBuilder}, the output would look like this:
   * <p/>
   * 
   * <pre>
   * Person[name=John Doe,age=33,smoker=false]
   * </pre>
   *
   * @since 2.1
   */
  public static final ToStringStyle SHORT_PREFIX_STYLE = new ShortPrefixToStringStyle();

  /**
   * The simple toString style. Using the Using the <code>Person</code> example from
   * {@link ToStringBuilder}, the output would look like this:
   * <p/>
   * 
   * <pre>
   * John Doe,33,false
   * </pre>
   */
  public static final ToStringStyle SIMPLE_STYLE = new SimpleToStringStyle();

  /**
   * <p>
   * A registry of objects used by <code>reflectionToString</code> methods to detect cyclical object
   * references and avoid infinite loops.
   * </p>
   */
  private static final ThreadLocal REGISTRY = new ThreadLocal();

  /**
   * <p>
   * Returns the registry of objects being traversed by the <code>reflectionToString</code> methods
   * in the current thread.
   * </p>
   *
   * @return Set the registry of objects being traversed
   */
  static Map getRegistry() {
    return (Map) REGISTRY.get();
  }

  /**
   * <p>
   * Returns <code>true</code> if the registry contains the given object. Used by the reflection
   * methods to avoid infinite loops.
   * </p>
   *
   * @param value The object to lookup in the registry.
   * @return boolean <code>true</code> if the registry contains the given object.
   */
  static boolean isRegistered(Object value) {
    Map m = getRegistry();
    return m != null && m.containsKey(value);
  }

  /**
   * <p>
   * Registers the given object. Used by the reflection methods to avoid infinite loops.
   * </p>
   *
   * @param value The object to register.
   */
  static void register(Object value) {
    if (value != null) {
      Map m = getRegistry();
      if (m == null) {
        m = new WeakHashMap();
        REGISTRY.set(m);
      }
      m.put(value, null);
    }
  }

  /**
   * <p>
   * Unregisters the given object.
   * </p>
   * <p/>
   * <p>
   * Used by the reflection methods to avoid infinite loops.
   * </p>
   *
   * @param value The object to unregister.
   */
  static void unregister(Object value) {
    if (value != null) {
      Map m = getRegistry();
      if (m != null) {
        m.remove(value);
        if (m.isEmpty()) {
          REGISTRY.set(null);
        }
      }
    }
  }

  /**
   * Whether to use the field names, the default is <code>true</code>.
   */
  private boolean useFieldNames = true;

  /**
   * Whether to use the class name, the default is <code>true</code>.
   */
  private boolean useClassName = true;

  /**
   * Whether to use short class names, the default is <code>false</code>.
   */
  private boolean useShortClassName = false;

  /**
   * Whether to use the identity hash code, the default is <code>true</code>.
   */
  private boolean useIdentityHashCode = true;

  /**
   * The content start <code>'['</code>.
   */
  private String contentStart = "[";

  /**
   * The content end <code>']'</code>.
   */
  private String contentEnd = "]";

  /**
   * The field name value separator <code>'='</code>.
   */
  private String fieldNameValueSeparator = "=";

  /**
   * Whether the field separator should be added before any other fields.
   */
  private boolean fieldSeparatorAtStart = false;

  /**
   * Whether the field separator should be added after any other fields.
   */
  private boolean fieldSeparatorAtEnd = false;

  /**
   * The field separator <code>','</code>.
   */
  private String fieldSeparator = ",";

  /**
   * The array start <code>'{'</code>.
   */
  private String arrayStart = "{";

  /**
   * The array separator <code>','</code>.
   */
  private String arraySeparator = ",";

  /**
   * The detail for array content.
   */
  private boolean arrayContentDetail = true;

  /**
   * The array end <code>'}'</code>.
   */
  private String arrayEnd = "}";

  /**
   * The value to use when fullDetail is <code>null</code>, the default value is <code>true</code>.
   */
  private boolean defaultFullDetail = true;

  /**
   * The <code>null</code> text <code>'&lt;null&gt;'</code>.
   */
  private String nullText = "<null>";

  /**
   * The summary size text start <code>'<size'</code>.
   */
  private String sizeStartText = "<size=";

  /**
   * The summary size text start <code>'&gt;'</code>.
   */
  private String sizeEndText = ">";

  /**
   * The summary object text start <code>'&lt;'</code>.
   */
  private String summaryObjectStartText = "<";

  /**
   * The summary object text start <code>'&gt;'</code>.
   */
  private String summaryObjectEndText = ">";

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * Constructor.
   * </p>
   */
  protected ToStringStyle() {
    super();
  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * Append to the <code>toString</code> the superclass toString.
   * </p>
   * <p>
   * NOTE: It assumes that the toString has been created from the same ToStringStyle.
   * </p>
   * <p/>
   * <p>
   * A <code>null</code> <code>superToString</code> is ignored.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param superToString the <code>super.toString()</code>
   * @since 2.0
   */
  public void appendSuper(StringBuffer buffer, String superToString) {
    appendToString(buffer, superToString);
  }

  /**
   * <p>
   * Append to the <code>toString</code> another toString.
   * </p>
   * <p>
   * NOTE: It assumes that the toString has been created from the same ToStringStyle.
   * </p>
   * <p/>
   * <p>
   * A <code>null</code> <code>toString</code> is ignored.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param toString the additional <code>toString</code>
   * @since 2.0
   */
  public void appendToString(StringBuffer buffer, String toString) {
    if (toString != null) {
      int pos1 = toString.indexOf(contentStart) + contentStart.length();
      int pos2 = toString.lastIndexOf(contentEnd);
      if (pos1 != pos2 && pos1 >= 0 && pos2 >= 0) {
        String data = toString.substring(pos1, pos2);
        if (fieldSeparatorAtStart) {
          removeLastFieldSeparator(buffer);
        }
        buffer.append(data);
        appendFieldSeparator(buffer);
      }
    }
  }

  /**
   * <p>
   * Append to the <code>toString</code> the start of data indicator.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param object the <code>Object</code> to build a <code>toString</code> for
   */
  public void appendStart(StringBuffer buffer, Object object) {
    if (object != null) {
      appendClassName(buffer, object);
      appendIdentityHashCode(buffer, object);
      appendContentStart(buffer);
      if (fieldSeparatorAtStart) {
        appendFieldSeparator(buffer);
      }
    }
  }

  /**
   * <p>
   * Append to the <code>toString</code> the end of data indicator.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param object the <code>Object</code> to build a <code>toString</code> for.
   */
  public void appendEnd(StringBuffer buffer, Object object) {
    if (this.fieldSeparatorAtEnd == false) {
      removeLastFieldSeparator(buffer);
    }
    appendContentEnd(buffer);
    unregister(object);
  }

  /**
   * <p>
   * Remove the last field separator from the buffer.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @since 2.0
   */
  protected void removeLastFieldSeparator(StringBuffer buffer) {
    int len = buffer.length();
    int sepLen = fieldSeparator.length();
    if (len > 0 && sepLen > 0 && len >= sepLen) {
      boolean match = true;
      for (int i = 0; i < sepLen; i++) {
        if (buffer.charAt(len - 1 - i) != fieldSeparator.charAt(sepLen - 1 - i)) {
          match = false;
          break;
        }
      }
      if (match) {
        buffer.setLength(len - sepLen);
      }
    }
  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * Append to the <code>toString</code> an <code>Object</code> value, printing the full
   * <code>toString</code> of the <code>Object</code> passed in.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name
   * @param value the value to add to the <code>toString</code>
   * @param fullDetail <code>true</code> for detail, <code>false</code> for summary info,
   *        <code>null</code> for style decides
   */
  public void append(StringBuffer buffer, String fieldName, Object value, Boolean fullDetail) {
    appendFieldStart(buffer, fieldName);

    if (value == null) {
      appendNullText(buffer, fieldName);

    } else {
      appendInternal(buffer, fieldName, value, isFullDetail(fullDetail));
    }

    appendFieldEnd(buffer, fieldName);
  }

  /**
   * <p>
   * Append to the <code>toString</code> an <code>Object</code>, correctly interpreting its type.
   * </p>
   * <p/>
   * <p>
   * This method performs the main lookup by Class type to correctly route arrays,
   * <code>Collections</code>, <code>Maps</code> and <code>Objects</code> to the appropriate method.
   * </p>
   * <p/>
   * <p>
   * Either detail or summary views can be specified.
   * </p>
   * <p/>
   * <p>
   * If a cycle is detected, an object will be appended with the <code>Object.toString()</code>
   * format.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param value the value to add to the <code>toString</code>, not <code>null</code>
   * @param detail output detail or not
   */
  protected void appendInternal(StringBuffer buffer, String fieldName, Object value, boolean detail) {
    if (isRegistered(value)
        && !(value instanceof Number || value instanceof Boolean || value instanceof Character)) {
      appendCyclicObject(buffer, fieldName, value);
      return;
    }

    register(value);

    try {
      if (value instanceof Collection) {
        if (detail) {
          appendDetail(buffer, fieldName, (Collection) value);
        } else {
          appendSummarySize(buffer, fieldName, ((Collection) value).size());
        }

      } else if (value instanceof Map) {
        if (detail) {
          appendDetail(buffer, fieldName, (Map) value);
        } else {
          appendSummarySize(buffer, fieldName, ((Map) value).size());
        }

      } else if (value instanceof long[]) {
        if (detail) {
          appendDetail(buffer, fieldName, (long[]) value);
        } else {
          appendSummary(buffer, fieldName, (long[]) value);
        }

      } else if (value instanceof int[]) {
        if (detail) {
          appendDetail(buffer, fieldName, (int[]) value);
        } else {
          appendSummary(buffer, fieldName, (int[]) value);
        }

      } else if (value instanceof short[]) {
        if (detail) {
          appendDetail(buffer, fieldName, (short[]) value);
        } else {
          appendSummary(buffer, fieldName, (short[]) value);
        }

      } else if (value instanceof byte[]) {
        if (detail) {
          appendDetail(buffer, fieldName, (byte[]) value);
        } else {
          appendSummary(buffer, fieldName, (byte[]) value);
        }

      } else if (value instanceof char[]) {
        if (detail) {
          appendDetail(buffer, fieldName, (char[]) value);
        } else {
          appendSummary(buffer, fieldName, (char[]) value);
        }

      } else if (value instanceof double[]) {
        if (detail) {
          appendDetail(buffer, fieldName, (double[]) value);
        } else {
          appendSummary(buffer, fieldName, (double[]) value);
        }

      } else if (value instanceof float[]) {
        if (detail) {
          appendDetail(buffer, fieldName, (float[]) value);
        } else {
          appendSummary(buffer, fieldName, (float[]) value);
        }

      } else if (value instanceof boolean[]) {
        if (detail) {
          appendDetail(buffer, fieldName, (boolean[]) value);
        } else {
          appendSummary(buffer, fieldName, (boolean[]) value);
        }

      } else if (value.getClass().isArray()) {
        if (detail) {
          appendDetail(buffer, fieldName, (Object[]) value);
        } else {
          appendSummary(buffer, fieldName, (Object[]) value);
        }

      } else {
        if (detail) {
          appendDetail(buffer, fieldName, value);
        } else {
          appendSummary(buffer, fieldName, value);
        }
      }
    } finally {
      unregister(value);
    }
  }

  /**
   * <p>
   * Append to the <code>toString</code> an <code>Object</code> value that has been detected to
   * participate in a cycle. This implementation will print the standard string value of the value.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param value the value to add to the <code>toString</code>, not <code>null</code>
   * @since 2.2
   */
  protected void appendCyclicObject(StringBuffer buffer, String fieldName, Object value) {
    identityToString(buffer, value);
  }

  /**
   * <p>
   * Append to the <code>toString</code> an <code>Object</code> value, printing the full detail of
   * the <code>Object</code>.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param value the value to add to the <code>toString</code>, not <code>null</code>
   */
  protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
    buffer.append(value);
  }

  /**
   * <p>
   * Append to the <code>toString</code> a <code>Collection</code>.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param coll the <code>Collection</code> to add to the <code>toString</code>, not
   *        <code>null</code>
   */
  protected void appendDetail(StringBuffer buffer, String fieldName, Collection coll) {
    buffer.append(coll);
  }

  /**
   * <p>
   * Append to the <code>toString</code> a <code>Map<code>.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param map the <code>Map</code> to add to the <code>toString</code>, not <code>null</code>
   */
  protected void appendDetail(StringBuffer buffer, String fieldName, Map map) {
    buffer.append(map);
  }

  /**
   * <p>
   * Append to the <code>toString</code> an <code>Object</code> value, printing a summary of the
   * <code>Object</code>.
   * </P>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param value the value to add to the <code>toString</code>, not <code>null</code>
   */
  protected void appendSummary(StringBuffer buffer, String fieldName, Object value) {
    buffer.append(summaryObjectStartText);
    buffer.append(getShortClassName(value.getClass()));
    buffer.append(summaryObjectEndText);
  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * Append to the <code>toString</code> a <code>long</code> value.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name
   * @param value the value to add to the <code>toString</code>
   */
  public void append(StringBuffer buffer, String fieldName, long value) {
    appendFieldStart(buffer, fieldName);
    appendDetail(buffer, fieldName, value);
    appendFieldEnd(buffer, fieldName);
  }

  /**
   * <p>
   * Append to the <code>toString</code> a <code>long</code> value.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param value the value to add to the <code>toString</code>
   */
  protected void appendDetail(StringBuffer buffer, String fieldName, long value) {
    buffer.append(value);
  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * Append to the <code>toString</code> an <code>int</code> value.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name
   * @param value the value to add to the <code>toString</code>
   */
  public void append(StringBuffer buffer, String fieldName, int value) {
    appendFieldStart(buffer, fieldName);
    appendDetail(buffer, fieldName, value);
    appendFieldEnd(buffer, fieldName);
  }

  /**
   * <p>
   * Append to the <code>toString</code> an <code>int</code> value.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param value the value to add to the <code>toString</code>
   */
  protected void appendDetail(StringBuffer buffer, String fieldName, int value) {
    buffer.append(value);
  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * Append to the <code>toString</code> a <code>short</code> value.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name
   * @param value the value to add to the <code>toString</code>
   */
  public void append(StringBuffer buffer, String fieldName, short value) {
    appendFieldStart(buffer, fieldName);
    appendDetail(buffer, fieldName, value);
    appendFieldEnd(buffer, fieldName);
  }

  /**
   * <p>
   * Append to the <code>toString</code> a <code>short</code> value.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param value the value to add to the <code>toString</code>
   */
  protected void appendDetail(StringBuffer buffer, String fieldName, short value) {
    buffer.append(value);
  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * Append to the <code>toString</code> a <code>byte</code> value.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name
   * @param value the value to add to the <code>toString</code>
   */
  public void append(StringBuffer buffer, String fieldName, byte value) {
    appendFieldStart(buffer, fieldName);
    appendDetail(buffer, fieldName, value);
    appendFieldEnd(buffer, fieldName);
  }

  /**
   * <p>
   * Append to the <code>toString</code> a <code>byte</code> value.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param value the value to add to the <code>toString</code>
   */
  protected void appendDetail(StringBuffer buffer, String fieldName, byte value) {
    buffer.append(value);
  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * Append to the <code>toString</code> a <code>char</code> value.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name
   * @param value the value to add to the <code>toString</code>
   */
  public void append(StringBuffer buffer, String fieldName, char value) {
    appendFieldStart(buffer, fieldName);
    appendDetail(buffer, fieldName, value);
    appendFieldEnd(buffer, fieldName);
  }

  /**
   * <p>
   * Append to the <code>toString</code> a <code>char</code> value.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param value the value to add to the <code>toString</code>
   */
  protected void appendDetail(StringBuffer buffer, String fieldName, char value) {
    buffer.append(value);
  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * Append to the <code>toString</code> a <code>double</code> value.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name
   * @param value the value to add to the <code>toString</code>
   */
  public void append(StringBuffer buffer, String fieldName, double value) {
    appendFieldStart(buffer, fieldName);
    appendDetail(buffer, fieldName, value);
    appendFieldEnd(buffer, fieldName);
  }

  /**
   * <p>
   * Append to the <code>toString</code> a <code>double</code> value.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param value the value to add to the <code>toString</code>
   */
  protected void appendDetail(StringBuffer buffer, String fieldName, double value) {
    buffer.append(value);
  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * Append to the <code>toString</code> a <code>float</code> value.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name
   * @param value the value to add to the <code>toString</code>
   */
  public void append(StringBuffer buffer, String fieldName, float value) {
    appendFieldStart(buffer, fieldName);
    appendDetail(buffer, fieldName, value);
    appendFieldEnd(buffer, fieldName);
  }

  /**
   * <p>
   * Append to the <code>toString</code> a <code>float</code> value.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param value the value to add to the <code>toString</code>
   */
  protected void appendDetail(StringBuffer buffer, String fieldName, float value) {
    buffer.append(value);
  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * Append to the <code>toString</code> a <code>boolean</code> value.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name
   * @param value the value to add to the <code>toString</code>
   */
  public void append(StringBuffer buffer, String fieldName, boolean value) {
    appendFieldStart(buffer, fieldName);
    appendDetail(buffer, fieldName, value);
    appendFieldEnd(buffer, fieldName);
  }

  /**
   * <p>
   * Append to the <code>toString</code> a <code>boolean</code> value.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param value the value to add to the <code>toString</code>
   */
  protected void appendDetail(StringBuffer buffer, String fieldName, boolean value) {
    buffer.append(value);
  }

  /**
   * <p>
   * Append to the <code>toString</code> an <code>Object</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name
   * @param array the array to add to the toString
   * @param fullDetail <code>true</code> for detail, <code>false</code> for summary info,
   *        <code>null</code> for style decides
   */
  public void append(StringBuffer buffer, String fieldName, Object[] array, Boolean fullDetail) {
    appendFieldStart(buffer, fieldName);

    if (array == null) {
      appendNullText(buffer, fieldName);

    } else if (isFullDetail(fullDetail)) {
      appendDetail(buffer, fieldName, array);

    } else {
      appendSummary(buffer, fieldName, array);
    }

    appendFieldEnd(buffer, fieldName);
  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * Append to the <code>toString</code> the detail of an <code>Object</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param array the array to add to the <code>toString</code>, not <code>null</code>
   */
  protected void appendDetail(StringBuffer buffer, String fieldName, Object[] array) {
    buffer.append(arrayStart);
    for (int i = 0; i < array.length; i++) {
      Object item = array[i];
      if (i > 0) {
        buffer.append(arraySeparator);
      }
      if (item == null) {
        appendNullText(buffer, fieldName);

      } else {
        appendInternal(buffer, fieldName, item, arrayContentDetail);
      }
    }
    buffer.append(arrayEnd);
  }

  /**
   * <p>
   * Append to the <code>toString</code> the detail of an array type.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param array the array to add to the <code>toString</code>, not <code>null</code>
   * @since 2.0
   */
  protected void reflectionAppendArrayDetail(StringBuffer buffer, String fieldName, Object array) {
    buffer.append(arrayStart);
    int length = Array.getLength(array);
    for (int i = 0; i < length; i++) {
      Object item = Array.get(array, i);
      if (i > 0) {
        buffer.append(arraySeparator);
      }
      if (item == null) {
        appendNullText(buffer, fieldName);

      } else {
        appendInternal(buffer, fieldName, item, arrayContentDetail);
      }
    }
    buffer.append(arrayEnd);
  }

  /**
   * <p>
   * Append to the <code>toString</code> a summary of an <code>Object</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param array the array to add to the <code>toString</code>, not <code>null</code>
   */
  protected void appendSummary(StringBuffer buffer, String fieldName, Object[] array) {
    appendSummarySize(buffer, fieldName, array.length);
  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * Append to the <code>toString</code> a <code>long</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name
   * @param array the array to add to the <code>toString</code>
   * @param fullDetail <code>true</code> for detail, <code>false</code> for summary info,
   *        <code>null</code> for style decides
   */
  public void append(StringBuffer buffer, String fieldName, long[] array, Boolean fullDetail) {
    appendFieldStart(buffer, fieldName);

    if (array == null) {
      appendNullText(buffer, fieldName);

    } else if (isFullDetail(fullDetail)) {
      appendDetail(buffer, fieldName, array);

    } else {
      appendSummary(buffer, fieldName, array);
    }

    appendFieldEnd(buffer, fieldName);
  }

  /**
   * <p>
   * Append to the <code>toString</code> the detail of a <code>long</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param array the array to add to the <code>toString</code>, not <code>null</code>
   */
  protected void appendDetail(StringBuffer buffer, String fieldName, long[] array) {
    buffer.append(arrayStart);
    for (int i = 0; i < array.length; i++) {
      if (i > 0) {
        buffer.append(arraySeparator);
      }
      appendDetail(buffer, fieldName, array[i]);
    }
    buffer.append(arrayEnd);
  }

  /**
   * <p>
   * Append to the <code>toString</code> a summary of a <code>long</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param array the array to add to the <code>toString</code>, not <code>null</code>
   */
  protected void appendSummary(StringBuffer buffer, String fieldName, long[] array) {
    appendSummarySize(buffer, fieldName, array.length);
  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * Append to the <code>toString</code> an <code>int</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name
   * @param array the array to add to the <code>toString</code>
   * @param fullDetail <code>true</code> for detail, <code>false</code> for summary info,
   *        <code>null</code> for style decides
   */
  public void append(StringBuffer buffer, String fieldName, int[] array, Boolean fullDetail) {
    appendFieldStart(buffer, fieldName);

    if (array == null) {
      appendNullText(buffer, fieldName);

    } else if (isFullDetail(fullDetail)) {
      appendDetail(buffer, fieldName, array);

    } else {
      appendSummary(buffer, fieldName, array);
    }

    appendFieldEnd(buffer, fieldName);
  }

  /**
   * <p>
   * Append to the <code>toString</code> the detail of an <code>int</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param array the array to add to the <code>toString</code>, not <code>null</code>
   */
  protected void appendDetail(StringBuffer buffer, String fieldName, int[] array) {
    buffer.append(arrayStart);
    for (int i = 0; i < array.length; i++) {
      if (i > 0) {
        buffer.append(arraySeparator);
      }
      appendDetail(buffer, fieldName, array[i]);
    }
    buffer.append(arrayEnd);
  }

  /**
   * <p>
   * Append to the <code>toString</code> a summary of an <code>int</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param array the array to add to the <code>toString</code>, not <code>null</code>
   */
  protected void appendSummary(StringBuffer buffer, String fieldName, int[] array) {
    appendSummarySize(buffer, fieldName, array.length);
  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * Append to the <code>toString</code> a <code>short</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name
   * @param array the array to add to the <code>toString</code>
   * @param fullDetail <code>true</code> for detail, <code>false</code> for summary info,
   *        <code>null</code> for style decides
   */
  public void append(StringBuffer buffer, String fieldName, short[] array, Boolean fullDetail) {
    appendFieldStart(buffer, fieldName);

    if (array == null) {
      appendNullText(buffer, fieldName);

    } else if (isFullDetail(fullDetail)) {
      appendDetail(buffer, fieldName, array);

    } else {
      appendSummary(buffer, fieldName, array);
    }

    appendFieldEnd(buffer, fieldName);
  }

  /**
   * <p>
   * Append to the <code>toString</code> the detail of a <code>short</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param array the array to add to the <code>toString</code>, not <code>null</code>
   */
  protected void appendDetail(StringBuffer buffer, String fieldName, short[] array) {
    buffer.append(arrayStart);
    for (int i = 0; i < array.length; i++) {
      if (i > 0) {
        buffer.append(arraySeparator);
      }
      appendDetail(buffer, fieldName, array[i]);
    }
    buffer.append(arrayEnd);
  }

  /**
   * <p>
   * Append to the <code>toString</code> a summary of a <code>short</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param array the array to add to the <code>toString</code>, not <code>null</code>
   */
  protected void appendSummary(StringBuffer buffer, String fieldName, short[] array) {
    appendSummarySize(buffer, fieldName, array.length);
  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * Append to the <code>toString</code> a <code>byte</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name
   * @param array the array to add to the <code>toString</code>
   * @param fullDetail <code>true</code> for detail, <code>false</code> for summary info,
   *        <code>null</code> for style decides
   */
  public void append(StringBuffer buffer, String fieldName, byte[] array, Boolean fullDetail) {
    appendFieldStart(buffer, fieldName);

    if (array == null) {
      appendNullText(buffer, fieldName);

    } else if (isFullDetail(fullDetail)) {
      appendDetail(buffer, fieldName, array);

    } else {
      appendSummary(buffer, fieldName, array);
    }

    appendFieldEnd(buffer, fieldName);
  }

  /**
   * <p>
   * Append to the <code>toString</code> the detail of a <code>byte</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param array the array to add to the <code>toString</code>, not <code>null</code>
   */
  protected void appendDetail(StringBuffer buffer, String fieldName, byte[] array) {
    buffer.append(arrayStart);
    for (int i = 0; i < array.length; i++) {
      if (i > 0) {
        buffer.append(arraySeparator);
      }
      appendDetail(buffer, fieldName, array[i]);
    }
    buffer.append(arrayEnd);
  }

  /**
   * <p>
   * Append to the <code>toString</code> a summary of a <code>byte</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param array the array to add to the <code>toString</code>, not <code>null</code>
   */
  protected void appendSummary(StringBuffer buffer, String fieldName, byte[] array) {
    appendSummarySize(buffer, fieldName, array.length);
  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * Append to the <code>toString</code> a <code>char</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name
   * @param array the array to add to the <code>toString</code>
   * @param fullDetail <code>true</code> for detail, <code>false</code> for summary info,
   *        <code>null</code> for style decides
   */
  public void append(StringBuffer buffer, String fieldName, char[] array, Boolean fullDetail) {
    appendFieldStart(buffer, fieldName);

    if (array == null) {
      appendNullText(buffer, fieldName);

    } else if (isFullDetail(fullDetail)) {
      appendDetail(buffer, fieldName, array);

    } else {
      appendSummary(buffer, fieldName, array);
    }

    appendFieldEnd(buffer, fieldName);
  }

  /**
   * <p>
   * Append to the <code>toString</code> the detail of a <code>char</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param array the array to add to the <code>toString</code>, not <code>null</code>
   */
  protected void appendDetail(StringBuffer buffer, String fieldName, char[] array) {
    buffer.append(arrayStart);
    for (int i = 0; i < array.length; i++) {
      if (i > 0) {
        buffer.append(arraySeparator);
      }
      appendDetail(buffer, fieldName, array[i]);
    }
    buffer.append(arrayEnd);
  }

  /**
   * <p>
   * Append to the <code>toString</code> a summary of a <code>char</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param array the array to add to the <code>toString</code>, not <code>null</code>
   */
  protected void appendSummary(StringBuffer buffer, String fieldName, char[] array) {
    appendSummarySize(buffer, fieldName, array.length);
  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * Append to the <code>toString</code> a <code>double</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name
   * @param array the array to add to the toString
   * @param fullDetail <code>true</code> for detail, <code>false</code> for summary info,
   *        <code>null</code> for style decides
   */
  public void append(StringBuffer buffer, String fieldName, double[] array, Boolean fullDetail) {
    appendFieldStart(buffer, fieldName);

    if (array == null) {
      appendNullText(buffer, fieldName);

    } else if (isFullDetail(fullDetail)) {
      appendDetail(buffer, fieldName, array);

    } else {
      appendSummary(buffer, fieldName, array);
    }

    appendFieldEnd(buffer, fieldName);
  }

  /**
   * <p>
   * Append to the <code>toString</code> the detail of a <code>double</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param array the array to add to the <code>toString</code>, not <code>null</code>
   */
  protected void appendDetail(StringBuffer buffer, String fieldName, double[] array) {
    buffer.append(arrayStart);
    for (int i = 0; i < array.length; i++) {
      if (i > 0) {
        buffer.append(arraySeparator);
      }
      appendDetail(buffer, fieldName, array[i]);
    }
    buffer.append(arrayEnd);
  }

  /**
   * <p>
   * Append to the <code>toString</code> a summary of a <code>double</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param array the array to add to the <code>toString</code>, not <code>null</code>
   */
  protected void appendSummary(StringBuffer buffer, String fieldName, double[] array) {
    appendSummarySize(buffer, fieldName, array.length);
  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * Append to the <code>toString</code> a <code>float</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name
   * @param array the array to add to the toString
   * @param fullDetail <code>true</code> for detail, <code>false</code> for summary info,
   *        <code>null</code> for style decides
   */
  public void append(StringBuffer buffer, String fieldName, float[] array, Boolean fullDetail) {
    appendFieldStart(buffer, fieldName);

    if (array == null) {
      appendNullText(buffer, fieldName);

    } else if (isFullDetail(fullDetail)) {
      appendDetail(buffer, fieldName, array);

    } else {
      appendSummary(buffer, fieldName, array);
    }

    appendFieldEnd(buffer, fieldName);
  }

  /**
   * <p>
   * Append to the <code>toString</code> the detail of a <code>float</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param array the array to add to the <code>toString</code>, not <code>null</code>
   */
  protected void appendDetail(StringBuffer buffer, String fieldName, float[] array) {
    buffer.append(arrayStart);
    for (int i = 0; i < array.length; i++) {
      if (i > 0) {
        buffer.append(arraySeparator);
      }
      appendDetail(buffer, fieldName, array[i]);
    }
    buffer.append(arrayEnd);
  }

  /**
   * <p>
   * Append to the <code>toString</code> a summary of a <code>float</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param array the array to add to the <code>toString</code>, not <code>null</code>
   */
  protected void appendSummary(StringBuffer buffer, String fieldName, float[] array) {
    appendSummarySize(buffer, fieldName, array.length);
  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * Append to the <code>toString</code> a <code>boolean</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name
   * @param array the array to add to the toString
   * @param fullDetail <code>true</code> for detail, <code>false</code> for summary info,
   *        <code>null</code> for style decides
   */
  public void append(StringBuffer buffer, String fieldName, boolean[] array, Boolean fullDetail) {
    appendFieldStart(buffer, fieldName);

    if (array == null) {
      appendNullText(buffer, fieldName);

    } else if (isFullDetail(fullDetail)) {
      appendDetail(buffer, fieldName, array);

    } else {
      appendSummary(buffer, fieldName, array);
    }

    appendFieldEnd(buffer, fieldName);
  }

  /**
   * <p>
   * Append to the <code>toString</code> the detail of a <code>boolean</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param array the array to add to the <code>toString</code>, not <code>null</code>
   */
  protected void appendDetail(StringBuffer buffer, String fieldName, boolean[] array) {
    buffer.append(arrayStart);
    for (int i = 0; i < array.length; i++) {
      if (i > 0) {
        buffer.append(arraySeparator);
      }
      appendDetail(buffer, fieldName, array[i]);
    }
    buffer.append(arrayEnd);
  }

  /**
   * <p>
   * Append to the <code>toString</code> a summary of a <code>boolean</code> array.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param array the array to add to the <code>toString</code>, not <code>null</code>
   */
  protected void appendSummary(StringBuffer buffer, String fieldName, boolean[] array) {
    appendSummarySize(buffer, fieldName, array.length);
  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * Append to the <code>toString</code> the class name.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param object the <code>Object</code> whose name to output
   */
  protected void appendClassName(StringBuffer buffer, Object object) {
    if (useClassName && object != null) {
      register(object);
      if (useShortClassName) {
        buffer.append(getShortClassName(object.getClass()));
      } else {
        buffer.append(object.getClass().getName());
      }
    }
  }

  /**
   * <p>
   * Append the {@link System#identityHashCode(Object)}.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param object the <code>Object</code> whose id to output
   */
  protected void appendIdentityHashCode(StringBuffer buffer, Object object) {
    if (this.isUseIdentityHashCode() && object != null) {
      register(object);
      buffer.append('@');
      buffer.append(Integer.toHexString(System.identityHashCode(object)));
    }
  }

  /**
   * <p>
   * Append to the <code>toString</code> the content start.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   */
  protected void appendContentStart(StringBuffer buffer) {
    buffer.append(contentStart);
  }

  /**
   * <p>
   * Append to the <code>toString</code> the content end.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   */
  protected void appendContentEnd(StringBuffer buffer) {
    buffer.append(contentEnd);
  }

  /**
   * <p>
   * Append to the <code>toString</code> an indicator for <code>null</code>.
   * </p>
   * <p/>
   * <p>
   * The default indicator is <code>'&lt;null&gt;'</code>.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   */
  protected void appendNullText(StringBuffer buffer, String fieldName) {
    buffer.append(nullText);
  }

  /**
   * <p>
   * Append to the <code>toString</code> the field separator.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   */
  protected void appendFieldSeparator(StringBuffer buffer) {
    buffer.append(fieldSeparator);
  }

  /**
   * <p>
   * Append to the <code>toString</code> the field start.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name
   */
  protected void appendFieldStart(StringBuffer buffer, String fieldName) {
    if (useFieldNames && fieldName != null) {
      buffer.append(fieldName);
      buffer.append(fieldNameValueSeparator);
    }
  }

  /**
   * <p>
   * Append to the <code>toString<code> the field end.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   */
  protected void appendFieldEnd(StringBuffer buffer, String fieldName) {
    appendFieldSeparator(buffer);
  }

  /**
   * <p>
   * Append to the <code>toString</code> a size summary.
   * </p>
   * <p/>
   * <p>
   * The size summary is used to summarize the contents of <code>Collections</code>,
   * <code>Maps</code> and arrays.
   * </p>
   * <p/>
   * <p>
   * The output consists of a prefix, the passed in size and a suffix.
   * </p>
   * <p/>
   * <p>
   * The default format is <code>'&lt;size=n&gt;'<code>.
   * </p>
   *
   * @param buffer the <code>StringBuffer</code> to populate
   * @param fieldName the field name, typically not used as already appended
   * @param size the size to append
   */
  protected void appendSummarySize(StringBuffer buffer, String fieldName, int size) {
    buffer.append(sizeStartText);
    buffer.append(size);
    buffer.append(sizeEndText);
  }

  /**
   * <p>
   * Is this field to be output in full detail.
   * </p>
   * <p/>
   * <p>
   * This method converts a detail request into a detail level. The calling code may request full
   * detail (<code>true</code>), but a subclass might ignore that and always return
   * <code>false</code>. The calling code may pass in <code>null</code> indicating that it doesn't
   * care about the detail level. In this case the default detail level is used.
   * </p>
   *
   * @param fullDetailRequest the detail level requested
   * @return whether full detail is to be shown
   */
  protected boolean isFullDetail(Boolean fullDetailRequest) {
    if (fullDetailRequest == null) {
      return defaultFullDetail;
    }
    return fullDetailRequest.booleanValue();
  }

  /**
   * <p>
   * Gets the short class name for a class.
   * </p>
   * <p/>
   * <p>
   * The short class name is the classname excluding the package name.
   * </p>
   *
   * @param cls the <code>Class</code> to get the short name of
   * @return the short name
   */
  protected String getShortClassName(Class cls) {
    return getShortClassName2(cls);
  }

  // Setters and getters for the customizable parts of the style
  // These methods are not expected to be overridden, except to make public
  // (They are not public so that immutable subclasses can be written)
  // ---------------------------------------------------------------------

  /**
   * <p>
   * Gets whether to use the class name.
   * </p>
   *
   * @return the current useClassName flag
   */
  protected boolean isUseClassName() {
    return useClassName;
  }

  /**
   * <p>
   * Sets whether to use the class name.
   * </p>
   *
   * @param useClassName the new useClassName flag
   */
  protected void setUseClassName(boolean useClassName) {
    this.useClassName = useClassName;
  }

  // ---------------------------------------------------------------------

  /**
   * <p>
   * Gets whether to output short or long class names.
   * </p>
   *
   * @return the current useShortClassName flag
   * @since 2.0
   */
  protected boolean isUseShortClassName() {
    return useShortClassName;
  }

  /**
   * <p>
   * Gets whether to output short or long class names.
   * </p>
   *
   * @return the current shortClassName flag
   * @deprecated Use {@link #isUseShortClassName()} Method will be removed in Commons Lang 3.0.
   */
  protected boolean isShortClassName() {
    return useShortClassName;
  }

  /**
   * <p>
   * Sets whether to output short or long class names.
   * </p>
   *
   * @param useShortClassName the new useShortClassName flag
   * @since 2.0
   */
  protected void setUseShortClassName(boolean useShortClassName) {
    this.useShortClassName = useShortClassName;
  }

  /**
   * <p>
   * Sets whether to output short or long class names.
   * </p>
   *
   * @param shortClassName the new shortClassName flag
   * @deprecated Use {@link #setUseShortClassName(boolean)} Method will be removed in Commons Lang
   *             3.0.
   */
  protected void setShortClassName(boolean shortClassName) {
    this.useShortClassName = shortClassName;
  }

  // ---------------------------------------------------------------------

  /**
   * <p>
   * Gets whether to use the identity hash code.
   * </p>
   *
   * @return the current useIdentityHashCode flag
   */
  protected boolean isUseIdentityHashCode() {
    return useIdentityHashCode;
  }

  /**
   * <p>
   * Sets whether to use the identity hash code.
   * </p>
   *
   * @param useIdentityHashCode the new useIdentityHashCode flag
   */
  protected void setUseIdentityHashCode(boolean useIdentityHashCode) {
    this.useIdentityHashCode = useIdentityHashCode;
  }

  // ---------------------------------------------------------------------

  /**
   * <p>
   * Gets whether to use the field names passed in.
   * </p>
   *
   * @return the current useFieldNames flag
   */
  protected boolean isUseFieldNames() {
    return useFieldNames;
  }

  /**
   * <p>
   * Sets whether to use the field names passed in.
   * </p>
   *
   * @param useFieldNames the new useFieldNames flag
   */
  protected void setUseFieldNames(boolean useFieldNames) {
    this.useFieldNames = useFieldNames;
  }

  // ---------------------------------------------------------------------

  /**
   * <p>
   * Gets whether to use full detail when the caller doesn't specify.
   * </p>
   *
   * @return the current defaultFullDetail flag
   */
  protected boolean isDefaultFullDetail() {
    return defaultFullDetail;
  }

  /**
   * <p>
   * Sets whether to use full detail when the caller doesn't specify.
   * </p>
   *
   * @param defaultFullDetail the new defaultFullDetail flag
   */
  protected void setDefaultFullDetail(boolean defaultFullDetail) {
    this.defaultFullDetail = defaultFullDetail;
  }

  // ---------------------------------------------------------------------

  /**
   * <p>
   * Gets whether to output array content detail.
   * </p>
   *
   * @return the current array content detail setting
   */
  protected boolean isArrayContentDetail() {
    return arrayContentDetail;
  }

  /**
   * <p>
   * Sets whether to output array content detail.
   * </p>
   *
   * @param arrayContentDetail the new arrayContentDetail flag
   */
  protected void setArrayContentDetail(boolean arrayContentDetail) {
    this.arrayContentDetail = arrayContentDetail;
  }

  // ---------------------------------------------------------------------

  /**
   * <p>
   * Gets the array start text.
   * </p>
   *
   * @return the current array start text
   */
  protected String getArrayStart() {
    return arrayStart;
  }

  /**
   * <p>
   * Sets the array start text.
   * </p>
   * <p/>
   * <p>
   * <code>null</code> is accepted, but will be converted to an empty String.
   * </p>
   *
   * @param arrayStart the new array start text
   */
  protected void setArrayStart(String arrayStart) {
    if (arrayStart == null) {
      arrayStart = "";
    }
    this.arrayStart = arrayStart;
  }

  // ---------------------------------------------------------------------

  /**
   * <p>
   * Gets the array end text.
   * </p>
   *
   * @return the current array end text
   */
  protected String getArrayEnd() {
    return arrayEnd;
  }

  /**
   * <p>
   * Sets the array end text.
   * </p>
   * <p/>
   * <p>
   * <code>null</code> is accepted, but will be converted to an empty String.
   * </p>
   *
   * @param arrayEnd the new array end text
   */
  protected void setArrayEnd(String arrayEnd) {
    if (arrayEnd == null) {
      arrayEnd = "";
    }
    this.arrayEnd = arrayEnd;
  }

  // ---------------------------------------------------------------------

  /**
   * <p>
   * Gets the array separator text.
   * </p>
   *
   * @return the current array separator text
   */
  protected String getArraySeparator() {
    return arraySeparator;
  }

  /**
   * <p>
   * Sets the array separator text.
   * </p>
   * <p/>
   * <p>
   * <code>null</code> is accepted, but will be converted to an empty String.
   * </p>
   *
   * @param arraySeparator the new array separator text
   */
  protected void setArraySeparator(String arraySeparator) {
    if (arraySeparator == null) {
      arraySeparator = "";
    }
    this.arraySeparator = arraySeparator;
  }

  // ---------------------------------------------------------------------

  /**
   * <p>
   * Gets the content start text.
   * </p>
   *
   * @return the current content start text
   */
  protected String getContentStart() {
    return contentStart;
  }

  /**
   * <p>
   * Sets the content start text.
   * </p>
   * <p/>
   * <p>
   * <code>null</code> is accepted, but will be converted to an empty String.
   * </p>
   *
   * @param contentStart the new content start text
   */
  protected void setContentStart(String contentStart) {
    if (contentStart == null) {
      contentStart = "";
    }
    this.contentStart = contentStart;
  }

  // ---------------------------------------------------------------------

  /**
   * <p>
   * Gets the content end text.
   * </p>
   *
   * @return the current content end text
   */
  protected String getContentEnd() {
    return contentEnd;
  }

  /**
   * <p>
   * Sets the content end text.
   * </p>
   * <p/>
   * <p>
   * <code>null</code> is accepted, but will be converted to an empty String.
   * </p>
   *
   * @param contentEnd the new content end text
   */
  protected void setContentEnd(String contentEnd) {
    if (contentEnd == null) {
      contentEnd = "";
    }
    this.contentEnd = contentEnd;
  }

  // ---------------------------------------------------------------------

  /**
   * <p>
   * Gets the field name value separator text.
   * </p>
   *
   * @return the current field name value separator text
   */
  protected String getFieldNameValueSeparator() {
    return fieldNameValueSeparator;
  }

  /**
   * <p>
   * Sets the field name value separator text.
   * </p>
   * <p/>
   * <p>
   * <code>null</code> is accepted, but will be converted to an empty String.
   * </p>
   *
   * @param fieldNameValueSeparator the new field name value separator text
   */
  protected void setFieldNameValueSeparator(String fieldNameValueSeparator) {
    if (fieldNameValueSeparator == null) {
      fieldNameValueSeparator = "";
    }
    this.fieldNameValueSeparator = fieldNameValueSeparator;
  }

  // ---------------------------------------------------------------------

  /**
   * <p>
   * Gets the field separator text.
   * </p>
   *
   * @return the current field separator text
   */
  protected String getFieldSeparator() {
    return fieldSeparator;
  }

  /**
   * <p>
   * Sets the field separator text.
   * </p>
   * <p/>
   * <p>
   * <code>null</code> is accepted, but will be converted to an empty String.
   * </p>
   *
   * @param fieldSeparator the new field separator text
   */
  protected void setFieldSeparator(String fieldSeparator) {
    if (fieldSeparator == null) {
      fieldSeparator = "";
    }
    this.fieldSeparator = fieldSeparator;
  }

  // ---------------------------------------------------------------------

  /**
   * <p>
   * Gets whether the field separator should be added at the start of each buffer.
   * </p>
   *
   * @return the fieldSeparatorAtStart flag
   * @since 2.0
   */
  protected boolean isFieldSeparatorAtStart() {
    return fieldSeparatorAtStart;
  }

  /**
   * <p>
   * Sets whether the field separator should be added at the start of each buffer.
   * </p>
   *
   * @param fieldSeparatorAtStart the fieldSeparatorAtStart flag
   * @since 2.0
   */
  protected void setFieldSeparatorAtStart(boolean fieldSeparatorAtStart) {
    this.fieldSeparatorAtStart = fieldSeparatorAtStart;
  }

  // ---------------------------------------------------------------------

  /**
   * <p>
   * Gets whether the field separator should be added at the end of each buffer.
   * </p>
   *
   * @return fieldSeparatorAtEnd flag
   * @since 2.0
   */
  protected boolean isFieldSeparatorAtEnd() {
    return fieldSeparatorAtEnd;
  }

  /**
   * <p>
   * Sets whether the field separator should be added at the end of each buffer.
   * </p>
   *
   * @param fieldSeparatorAtEnd the fieldSeparatorAtEnd flag
   * @since 2.0
   */
  protected void setFieldSeparatorAtEnd(boolean fieldSeparatorAtEnd) {
    this.fieldSeparatorAtEnd = fieldSeparatorAtEnd;
  }

  // ---------------------------------------------------------------------

  /**
   * <p>
   * Gets the text to output when <code>null</code> found.
   * </p>
   *
   * @return the current text to output when null found
   */
  protected String getNullText() {
    return nullText;
  }

  /**
   * <p>
   * Sets the text to output when <code>null</code> found.
   * </p>
   * <p/>
   * <p>
   * <code>null</code> is accepted, but will be converted to an empty String.
   * </p>
   *
   * @param nullText the new text to output when null found
   */
  protected void setNullText(String nullText) {
    if (nullText == null) {
      nullText = "";
    }
    this.nullText = nullText;
  }

  // ---------------------------------------------------------------------

  /**
   * <p>
   * Gets the start text to output when a <code>Collection</code>, <code>Map</code> or array size is
   * output.
   * </p>
   * <p/>
   * <p>
   * This is output before the size value.
   * </p>
   *
   * @return the current start of size text
   */
  protected String getSizeStartText() {
    return sizeStartText;
  }

  /**
   * <p>
   * Sets the start text to output when a <code>Collection</code>, <code>Map</code> or array size is
   * output.
   * </p>
   * <p/>
   * <p>
   * This is output before the size value.
   * </p>
   * <p/>
   * <p>
   * <code>null</code> is accepted, but will be converted to an empty String.
   * </p>
   *
   * @param sizeStartText the new start of size text
   */
  protected void setSizeStartText(String sizeStartText) {
    if (sizeStartText == null) {
      sizeStartText = "";
    }
    this.sizeStartText = sizeStartText;
  }

  // ---------------------------------------------------------------------

  /**
   * <p>
   * Gets the end text to output when a <code>Collection</code>, <code>Map</code> or array size is
   * output.
   * </p>
   * <p/>
   * <p>
   * This is output after the size value.
   * </p>
   *
   * @return the current end of size text
   */
  protected String getSizeEndText() {
    return sizeEndText;
  }

  /**
   * <p>
   * Sets the end text to output when a <code>Collection</code>, <code>Map</code> or array size is
   * output.
   * </p>
   * <p/>
   * <p>
   * This is output after the size value.
   * </p>
   * <p/>
   * <p>
   * <code>null</code> is accepted, but will be converted to an empty String.
   * </p>
   *
   * @param sizeEndText the new end of size text
   */
  protected void setSizeEndText(String sizeEndText) {
    if (sizeEndText == null) {
      sizeEndText = "";
    }
    this.sizeEndText = sizeEndText;
  }

  // ---------------------------------------------------------------------

  /**
   * <p>
   * Gets the start text to output when an <code>Object</code> is output in summary mode.
   * </p>
   * <p/>
   * <p>
   * This is output before the size value.
   * </p>
   *
   * @return the current start of summary text
   */
  protected String getSummaryObjectStartText() {
    return summaryObjectStartText;
  }

  /**
   * <p>
   * Sets the start text to output when an <code>Object</code> is output in summary mode.
   * </p>
   * <p/>
   * <p>
   * This is output before the size value.
   * </p>
   * <p/>
   * <p>
   * <code>null</code> is accepted, but will be converted to an empty String.
   * </p>
   *
   * @param summaryObjectStartText the new start of summary text
   */
  protected void setSummaryObjectStartText(String summaryObjectStartText) {
    if (summaryObjectStartText == null) {
      summaryObjectStartText = "";
    }
    this.summaryObjectStartText = summaryObjectStartText;
  }

  // ---------------------------------------------------------------------

  /**
   * <p>
   * Gets the end text to output when an <code>Object</code> is output in summary mode.
   * </p>
   * <p/>
   * <p>
   * This is output after the size value.
   * </p>
   *
   * @return the current end of summary text
   */
  protected String getSummaryObjectEndText() {
    return summaryObjectEndText;
  }

  /**
   * <p>
   * Sets the end text to output when an <code>Object</code> is output in summary mode.
   * </p>
   * <p/>
   * <p>
   * This is output after the size value.
   * </p>
   * <p/>
   * <p>
   * <code>null</code> is accepted, but will be converted to an empty String.
   * </p>
   *
   * @param summaryObjectEndText the new end of summary text
   */
  protected void setSummaryObjectEndText(String summaryObjectEndText) {
    if (summaryObjectEndText == null) {
      summaryObjectEndText = "";
    }
    this.summaryObjectEndText = summaryObjectEndText;
  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * Default <code>ToStringStyle</code>.
   * </p>
   * <p/>
   * <p>
   * This is an inner class rather than using <code>StandardToStringStyle</code> to ensure its
   * immutability.
   * </p>
   */
  private static final class DefaultToStringStyle extends ToStringStyle {

    /**
     * Required for serialization support.
     *
     * @see java.io.Serializable
     */
    private static final long serialVersionUID = 1L;

    /**
     * <p>
     * Constructor.
     * </p>
     * <p/>
     * <p>
     * Use the static constant rather than instantiating.
     * </p>
     */
    DefaultToStringStyle() {
      super();
    }

    /**
     * <p>
     * Ensure <code>Singleton</code> after serialization.
     * </p>
     *
     * @return the singleton
     */
    private Object readResolve() {
      return ToStringStyle.DEFAULT_STYLE;
    }

  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * <code>ToStringStyle</code> that does not print out the field names.
   * </p>
   * <p/>
   * <p>
   * This is an inner class rather than using <code>StandardToStringStyle</code> to ensure its
   * immutability.
   */
  private static final class NoFieldNameToStringStyle extends ToStringStyle {

    private static final long serialVersionUID = 1L;

    /**
     * <p>
     * Constructor.
     * </p>
     * <p/>
     * <p>
     * Use the static constant rather than instantiating.
     * </p>
     */
    NoFieldNameToStringStyle() {
      super();
      this.setUseFieldNames(false);
    }

    /**
     * <p>
     * Ensure <code>Singleton</code> after serialization.
     * </p>
     *
     * @return the singleton
     */
    private Object readResolve() {
      return ToStringStyle.NO_FIELD_NAMES_STYLE;
    }

  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * <code>ToStringStyle</code> that prints out the short class name and no identity hashcode.
   * </p>
   * <p/>
   * <p>
   * This is an inner class rather than using <code>StandardToStringStyle</code> to ensure its
   * immutability.
   * </p>
   */
  private static final class ShortPrefixToStringStyle extends ToStringStyle {

    private static final long serialVersionUID = 1L;

    /**
     * <p>
     * Constructor.
     * </p>
     * <p/>
     * <p>
     * Use the static constant rather than instantiating.
     * </p>
     */
    ShortPrefixToStringStyle() {
      super();
      this.setUseShortClassName(true);
      this.setUseIdentityHashCode(false);
    }

    /**
     * <p>
     * Ensure <code>Singleton</ode> after serialization.
     * </p>
     *
     * @return the singleton
     */
    private Object readResolve() {
      return ToStringStyle.SHORT_PREFIX_STYLE;
    }

  }

  /**
   * <p>
   * <code>ToStringStyle</code> that does not print out the classname, identity hashcode, content
   * start or field name.
   * </p>
   * <p/>
   * <p>
   * This is an inner class rather than using <code>StandardToStringStyle</code> to ensure its
   * immutability.
   * </p>
   */
  private static final class SimpleToStringStyle extends ToStringStyle {

    private static final long serialVersionUID = 1L;

    /**
     * <p>
     * Constructor.
     * </p>
     * <p/>
     * <p>
     * Use the static constant rather than instantiating.
     * </p>
     */
    SimpleToStringStyle() {
      super();
      this.setUseClassName(false);
      this.setUseIdentityHashCode(false);
      this.setUseFieldNames(false);
      this.setContentStart("");
      this.setContentEnd("");
    }

    /**
     * <p>
     * Ensure <code>Singleton</ode> after serialization.
     * </p>
     *
     * @return the singleton
     */
    private Object readResolve() {
      return ToStringStyle.SIMPLE_STYLE;
    }

  }

  // ----------------------------------------------------------------------------

  /**
   * <p>
   * <code>ToStringStyle</code> that outputs on multiple lines.
   * </p>
   * <p/>
   * <p>
   * This is an inner class rather than using <code>StandardToStringStyle</code> to ensure its
   * immutability.
   * </p>
   */
  private static final class MultiLineToStringStyle extends ToStringStyle {

    private static final long serialVersionUID = 1L;

    /**
     * <p>
     * Constructor.
     * </p>
     * <p/>
     * <p>
     * Use the static constant rather than instantiating.
     * </p>
     */
    MultiLineToStringStyle() {
      super();
      this.setContentStart("[");
      this.setFieldSeparator(LINE_SEPARATOR + "  ");
      this.setFieldSeparatorAtStart(true);
      this.setContentEnd(LINE_SEPARATOR + "]");
    }

    /**
     * <p>
     * Ensure <code>Singleton</code> after serialization.
     * </p>
     *
     * @return the singleton
     */
    private Object readResolve() {
      return ToStringStyle.MULTI_LINE_STYLE;
    }

  }

  public static void identityToString(StringBuffer buffer, Object object) {
    if (object == null) {
      throw new NullPointerException("Cannot get the toString of a null identity");
    }
    buffer.append(object.getClass().getName()).append('@')
        .append(Integer.toHexString(System.identityHashCode(object)));
  }

  public static final String EMPTY = "";
  public static final char PACKAGE_SEPARATOR_CHAR = '.';
  public static final char INNER_CLASS_SEPARATOR_CHAR = '$';
  private static final Map reverseAbbreviationMap = new HashMap();

  public static String getShortClassName2(Class cls) {
    if (cls == null) {
      return EMPTY;
    }
    return getShortClassName(cls.getName());
  }

  public static String getShortClassName(String className) {
    if (className == null) {
      return EMPTY;
    }
    if (className.length() == 0) {
      return EMPTY;
    }

    StringBuilder arrayPrefix = new StringBuilder();
    // Handle array encoding
    if (className.startsWith("[")) {
      while (className.charAt(0) == '[') {
        className = className.substring(1);
        arrayPrefix.append("[]");
      }
      // Strip Object type encoding
      if (className.charAt(0) == 'L' && className.charAt(className.length() - 1) == ';') {
        className = className.substring(1, className.length() - 1);
      }
    }

    if (reverseAbbreviationMap.containsKey(className)) {
      className = (String) reverseAbbreviationMap.get(className);
    }

    int lastDotIdx = className.lastIndexOf(PACKAGE_SEPARATOR_CHAR);
    int innerIdx =
        className.indexOf(INNER_CLASS_SEPARATOR_CHAR, lastDotIdx == -1 ? 0 : lastDotIdx + 1);
    String out = className.substring(lastDotIdx + 1);
    if (innerIdx != -1) {
      out = out.replace(INNER_CLASS_SEPARATOR_CHAR, PACKAGE_SEPARATOR_CHAR);
    }
    return out + arrayPrefix;
  }
}
